﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Components.PhotonMapping;
using RayDen.Library.Components.Surface;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Components;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine.Engines.ProgressivePhotonMapping
{
    public class PPMEngineContext
    {

        private readonly object build_lock = new object();

        protected internal List<PPMHitPoint> hitPoints;
        protected internal IEyePointHashGrid Grid;
        protected IRandomProvider rnd;
        protected HitPointSampler eyePointSampler;
        protected SurfaceSampler SurfaceSampler;

        #region PhotonSampler
        protected class HitPointSampler : PathSamplingEngine<PPMHitPoint>
        {
            protected internal int MaxHitPoints, MaxCausticPhotons;
            protected IRandomProvider rnd;
            protected List<PPMHitPoint> hitPoints;
            protected new RayEngineScene scene;

            protected int width, height;

            public HitPointSampler(IIntersectionEngine engine, IRayEngineScene scn, IRadianceSampler ss,
                IRandomProvider r, int maxPoints, int w, int h)
                : base(scn, engine)
            {
                width = w;
                height = h;
                this.hitPoints = new List<PPMHitPoint>();

                this.SurfaceSampler = ss;
                this.intersectionEngine = engine;
                this.scene = (RayEngineScene)scn;

                this.rnd = r;
                MaxPhotonDepth = scene.MaxPathDepth;
                this.MaxHitPoints = maxPoints;
            }


            protected override PathInfo GeneratePhoton(IRayEngineScene scn, IRandomProvider rnd)
            {
                RayData ray;
                var scrX = (int)Math.Round(rnd.NextFloat() * width);
                var scrY = (int)Math.Round(rnd.NextFloat() * height);

                scene.Camera.GetRay(scrX, scrY, out ray);

                return new PathInfo() { Ray = ray, Throughput = new RgbSpectrum(1f), Data = new PPMHitPoint() { scrX = scrX, scrY = scrY, throughput = RgbSpectrum.UnitSpectrum() } };
            }

            protected override bool IsSamplingDone()
            {
                var cl = Console.CursorLeft;
                var ct = Console.CursorTop;
                var done = hitPoints.Count >= MaxHitPoints;
                Console.Write("{0} ", hitPoints.Count);
                Console.SetCursorPosition(cl, ct);
                return done;
            }

            protected override bool OnSamplingDone()
            {
                return true;
            }

            protected override void OnMiss(ref RayHit hit, PathInfo pathInfo)
            {
                pathInfo.Data.type = HitPointType.Constant;
                if (hit.Miss() && scene.EnvironmentMap != null)
                {
                    var wo = -pathInfo.Ray.Dir;
                    pathInfo.Data.throughput *= (RgbSpectrum)(scene.EnvironmentMap.Le(ref wo));
                }
                this.hitPoints.Add(pathInfo.Data);
            }

            protected override bool OnPathHit(ref Point hitPoint, ref Vector wo, ref Vector wi, SurfaceIntersectionData hitInfo, ref RgbSpectrum f, float fPdf, PathInfo photonPath)
            {
                if (f.IsBlack() || fPdf <= 0f)
                {
                    return false;
                }

                if (hitInfo.MMaterial.IsDiffuse())
                {
                    var hp = photonPath.Data;
                    hp.type = HitPointType.Surface;
                    hp.material = hitInfo.MMaterial;
                    hp.throughput = f * photonPath.Throughput;
                    //hp.pass = photons.;
                    hp.position = hitPoint;
                    hp.wo = wo;
                    hp.normal = hitInfo.ShadingNormal;
                    this.hitPoints.Add(hp);
                    //if (hitInfo.MMaterial.Type.HasFlag(BrdfType.Glossy))
                    return false;
                }

                RgbSpectrum anew = photonPath.Throughput * f * Vector.AbsDot(ref wi, ref hitInfo.ShadingNormal) / fPdf;
                float continueProb = Math.Min(1f, anew.y() / photonPath.Throughput.y());
                if (rnd.NextFloat() > continueProb || photonPath.Depth > MaxPhotonDepth || photonPath.Throughput.IsBlack())
                {
                    return false;
                }

                photonPath.Throughput *= anew / continueProb;

                photonPath.Ray = new RayData(ref hitPoint, ref wi);
                return true;
            }

        }
        #endregion



        public void BuildGatherPoints(IIntersectionEngine intersectionDevice, RayEngineScene scene)
        {
            int Width = 1280;
            int Height = 720;
            int maxPoints = Width * Height * 2;

            eyePointSampler = new HitPointSampler(intersectionDevice, scene, SurfaceSampler, rnd, maxPoints, Width, Height);

            lock (build_lock)
            {
                Tracer.TraceLine("Start building eye points hashgrid");
                if (hitPoints == null) 
                    hitPoints = new List<PPMHitPoint>(MaxPoints);
                Grid = new Grid(Width*Height,hitPoints);
                Grid.ReHash();
                Tracer.TraceLine("Complete!");
            }
        }


        public int MaxPoints { get { return 1280*720*2; } }
    }
}
